Skip to content

Capture handler refs at config construction rather than transport#2837

Open
laghee wants to merge 1 commit into
mainfrom
kmC/webkit-transport-snapshot-at-config-7465
Open

Capture handler refs at config construction rather than transport#2837
laghee wants to merge 1 commit into
mainfrom
kmC/webkit-transport-snapshot-at-config-7465

Conversation

@laghee

@laghee laghee commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Asana Task/Github Issue: https://app.asana.com/1/137249556945/task/1212684734587032?focus=true

Description

WebkitMessagingTransport previously captured handler references in its own constructor. Since ContentFeature.messaging is lazy, transport construction can happen after ContentFeature init has run, which includes after apiManipulation has applied any prototype-side messageHandlers-nullifying patches.

This PR moves the snapshot into WebkitMessagingConfig's constructor. Entry points construct the config eagerly at C-S-S bootstrap, before feature init runs, so the snapshot pre-dates any in-C-S-S apiManipulation patch. Transport constructor reads config.capturedHandlers rather than re-reading window.webkit.messageHandlers.

Adds a regression test: construct config, nullify
window.webkit.messageHandlers, construct transport, send message — send goes through the snapshot and reaches the original handler.

Testing Steps

Checklist

Please tick all that apply:

  • I have tested this change locally
  • I have tested this change locally in all supported browsers
  • This change will be visible to users
  • I have added automated tests that cover this change
  • I have ensured the change is gated by config
  • This change was covered by a ship review
  • This change was covered by a tech design
  • Any dependent config has been merged

…ransport construction

WebkitMessagingTransport previously captured handler references in its
own constructor. Since ContentFeature.messaging is lazy, transport
construction can happen after ContentFeature init has run, which
includes after apiManipulation has applied any prototype-side
messageHandlers-nullifying patches. Constructor then reads undefined
from window.webkit.messageHandlers and throws MissingHandler.

Move the snapshot into WebkitMessagingConfig's constructor. Entry
points construct the config eagerly at C-S-S bootstrap, before feature
init runs, so the snapshot pre-dates any in-C-S-S apiManipulation
patch. Transport constructor reads config.capturedHandlers rather than
re-reading window.webkit.messageHandlers.

Adds a regression test: construct config, nullify
window.webkit.messageHandlers, construct transport, send message —
send goes through the snapshot and reaches the original handler.
@laghee laghee requested a review from jonathanKingston July 3, 2026 16:28
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

⚠️ Cursor assessed this PR as unknown Risk (only Low Risk is auto-approved).

This PR requires a manual review and approval from a member of one of the following teams:

  • @duckduckgo/content-scope-scripts-owners
  • @duckduckgo/apple-devs
  • @duckduckgo/android-devs
  • @duckduckgo/team-windows-development
  • @duckduckgo/extension-owners
  • @duckduckgo/config-aor
  • @duckduckgo/breakage-aor
  • @duckduckgo/breakage

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

[Beta] Generated file diff

Time updated: Fri, 03 Jul 2026 16:29:48 GMT

Android
    - android/adsjsContentScope.js
  • android/autofillImport.js
  • android/brokerProtection.js
  • android/contentScope.js
  • android/duckAiChatHistory.js
  • android/duckAiDataClearing.js
  • android/pages/duckplayer/dist/index.js

File has changed

Apple
    - apple/contentScope.js
  • apple/contentScopeIsolated.js
  • apple/duckAiChatHistory.js
  • apple/duckAiDataClearing.js
  • apple/pages/duckplayer/dist/index.js
  • apple/pages/duckplayer/index.html
  • apple/pages/history/dist/index.js
  • apple/pages/new-tab/dist/index.js
  • apple/pages/onboarding/dist/index.js
  • apple/pages/release-notes/dist/index.js
  • apple/pages/special-error/dist/index.js
  • apple/pages/special-error/index.html

File has changed

Chrome-mv3
    - chrome-mv3/inject.js

File has changed

Firefox
    - firefox/inject.js

File has changed

Integration
    - integration/contentScope.js
  • integration/pages/duckplayer/dist/index.js
  • integration/pages/example/dist/index.js
  • integration/pages/history/dist/index.js
  • integration/pages/new-tab/dist/index.js
  • integration/pages/onboarding/dist/index.js
  • integration/pages/release-notes/dist/index.js
  • integration/pages/special-error/dist/index.js

File has changed

Windows
    - windows/contentScope.js
  • windows/pages/duckplayer/dist/index.js
  • windows/pages/history/dist/index.js
  • windows/pages/new-tab/dist/index.js
  • windows/pages/onboarding/dist/index.js
  • windows/pages/release-notes/dist/index.js
  • windows/pages/special-error/dist/index.js
  • windows/pages/special-error/index.html

File has changed

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Injected PR Evaluation: Web Compatibility & Security

PR moves WebKit handler snapshot from lazy WebkitMessagingTransport construction to eager WebkitMessagingConfig construction at C-S-S bootstrap. This fixes a production bug where apiManipulation can nullify window.webkit.messageHandlers during feature init() before any feature first accesses ContentFeature.messaging, causing transport construction to read an absent namespace and throw MissingHandler.

Verified locally: all 9 specs in messaging-webkit.spec.js pass.


Web Compatibility Assessment

File Lines Severity Finding
messaging/lib/webkit.js 40–46, 184–195 info No page-visible API surface is modified. wkSend dispatch semantics are unchanged (ReflectApply on captured handler pair). This is an internal messaging reliability fix.
messaging/lib/webkit.js 164–170 info Positive: Snapshot at bootstrap (before load()/init() in apple.js) restores native messaging on sites where in-C-S-S hardening nullifies messageHandlers before lazy transport construction. Without this, features that defer this.messaging access would silently fail to reach native.
injected/entry-points/apple.js 18–24 info Bootstrap ordering (WebkitMessagingConfigload()init()) matches the fix contract. No ordering regression expected on Apple platforms.
special-pages/shared/create-special-page-messaging.js 38–41 info Special pages construct config and Messaging synchronously in the same call; no lazy gap. Behavior unchanged but consistent with the new model.

No web-compat errors or warnings identified.


Security Assessment

File Lines Severity Finding
messaging/lib/webkit.js 57–64 info Send path unchanged: ReflectApply(captured.postMessage, captured.handler, [data]) avoids page-mutable .call/.bind.
messaging/lib/webkit.js 187–188 info Null-prototype cache preserved via { __proto__: null } literal (not Object.create(null)), resisting Object.create tampering at lazy-construction time. Existing unit test still covers this.
messaging/lib/webkit.js 184–195 info _snapshotWebkitHandlers reads window.webkit.messageHandlers via uncaptured window — same pattern as the removed captureWebkitHandlers. Acceptable because config is constructed at C-S-S bootstrap before page scripts and feature init; earlier snapshot actually narrows the tampering window vs deferred transport capture.
messaging/lib/webkit.js 43–46 info capturedHandlers === null guard fails fast at transport construction. An empty-but-truthy snapshot ({ __proto__: null } with no entries) still creates transport and fails per-handler at wkSend — same as prior captureWebkitHandlers behavior.
messaging/lib/webkit.js 110–129 info subscribe() still reads ensureNavigatorDuckDuckGo().messageHandlers at subscription time (unchanged, out of scope).

No security errors, warnings, or critical findings.


Risk Level

Medium Risk — This changes messaging transport initialization timing, which the rubric flags as transport-layer work, but the send path, trust boundaries, origin validation, and page-visible API contracts are unchanged; the diff is a well-tested relocation of an existing snapshot with preserved hardening.


Recommendations

  1. (info) Consider a brief JSDoc note on WebkitMessagingConfig that consumers must construct it at bootstrap (before feature init()), not lazily alongside transport — the snapshot contract is now implicit.
  2. (info) Optional unit test: capturedHandlers === null → transport constructor throws MissingHandler with the new message string.
  3. (info) No additional integration coverage required; the new regression test accurately models the production lazy-transport gap.

Verdict: Approve from a web-compat and security perspective. The change correctly fixes the bootstrap-vs-init race while retaining existing transport hardening.

Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

Comment thread messaging/lib/webkit.js
Comment thread injected/unit-test/messaging-webkit.spec.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants